BiblioAdmin model binding 1 op 1
In deze les leren we hoe je het model en de view aanpast voor een property die een foreign-key is. Uitleg hierover vind je op Relationships (10/27/2016). Een praktisch voorbeeld: Saineshwar Bageri, Binding Dropdown List With Database In ASP.NET Core MVC, Dec 12 2016
Video
Probleem
- In de tabel
Order
zitten er een kolommen met een foreign-key, namelijk:CustomerId
verwijst naar deCustomer
tabelShippingMethodId
verwijst naar de ShippingMethod tabelOrderStatusId
verwijst naar de OrderStatus tabel
- In de tabel
OrderItem
zitten er ook een kolommen met een foreign-key, namelijk:OrderId
verwijst naar de Order tabelBookId
verwijst naar de Book tabel
Oplossing
Als voorbeeld nemen we Order
.
- Wat gaat hieraan vooraf:
- Je hebt de CRUD pagina's van vorige les gemaakt.
- Als je daarmee nog problemen hebt, vind je een voorbeeldoplossing op mijn Bitbucket.
- Je hebt data ingevoerd in de tabel
ShippingMethod
,OrderStatus
enBook
. - We beginnen met onszelf of een fictieve klant toe te voegen (gebruik daarvoor de InsertingOne pagina die we vorige week gemaakt hebben):
- We voegen een order toe zonder de foreign key waarden te tonen:
- We voegen data annotation toe om een kalender te tonen in het formulier voor de datums:
[DataType(DataType.Date)] [Column(TypeName = "datetime")] public DateTime OrderDate { get; set; } [DataType(DataType.Date)] [Column(TypeName = "datetime")] public DateTime ShippingDate { get; set; }
- We runnen de web app en vullen het orderformulier in:
Met dit als resultaat:
- We voegen data annotation toe om een kalender te tonen in het formulier voor de datums:
- In de
Order/ReadingOne
Razor Page willen we de namen vanCustomer
,OrderStatus
enShippingMethod
tonen en niet deId
:- We passen de Bll/model klasse van
Order
aan. We voegen drie properties toe om de namen van de klanten, verzendmethoden en orderstatus bij te houden. Let op het gebruik van de data annotatie [NotMapped] om aan te geven dat die niet gebonden moeten worden aan het model wanneer deze waarden gepost worden.using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace BiblioAdmin.Bll { public partial class Order { [DataType(DataType.Date)] [Column(TypeName = "datetime")] public DateTime OrderDate { get; set; } [DataType(DataType.Date)] [Column(TypeName = "datetime")] public DateTime ShippingDate { get; set; } [Column(TypeName = "varchar(512)")] public string Comment { get; set; } [Column(TypeName = "int(11)")] public int Id { get; set; } [Column(TypeName = "int(11)")] public int CustomerId { get; set; } [Column(TypeName = "int(11)")] public int ShippingMethodId { get; set; } [Column(TypeName = "int(11)")] public int StatusId { get; set; } [NotMapped] [ForeignKey("CustomerId")] public Customer Customer { get; set; } [NotMapped] [ForeignKey("ShippingMethodId")] public ShippingMethod ShippingMethod { get; set; } [NotMapped] [ForeignKey("StatusId")] public OrderStatus Status { get; set; } } }
- De namen kunnen Latijnse karakters bevatten zoals é, è enz. Om die correct uit de database in te lezen moeten we het model van
Country
aanpassen. Daarvoor heb je deMySql.Data.EntityFrameworkCore.DataAnnotations
namespace nodig (MySQL Configuring Character Sets and Collations in EF Core):[MySqlCharset("latin1")] [NotMapped] [ForeignKey("CustomerId")] public Customer Customer { get; set; }
- In de
Onget
methode van deReading
klasse in het Order/ReadingOne.cshtml.cs bestand, halen we de rijnen van deOneModel
OrderStatus
,OrderShipping
enCustomer
tabel op en zetten ze in de overeenkomstige properties:public void OnGet(int? id) { this.Order = dbContext.Order.SingleOrDefault(m => m.Id == id); Order.ShippingMethod = dbContext.ShippingMethod.SingleOrDefault(m => m.Id == this.Order.ShippingMethodId); Order.Status = dbContext.OrderStatus.SingleOrDefault(m => m.Id == this.Order.StatusId); Order.Customer = dbContext.Customer.SingleOrDefault(m => m.Id == this.Order.CustomerId); OrderList = dbContext.Order.ToList(); }
- In de
ReadingOne
view van Order vangen we het model op en tonen de naam van deShippingMethod
,OrderStatus
enCustomer
in de HTML:<div> <label for="Order-Name">Klant</label> <input id="Order-Name" name="Order-Name" type="text" value="@Model.Order.Customer.FirstName @Model.Order.Customer.LastName" readonly> </div> <div> <label for="Order-Name">Verzendingsmethode</label> <input id="Order-Name" name="Order-Name" type="text" value="@Model.Order.ShippingMethod.Name" readonly> </div> <div> <label for="Order-Name">Status</label> <input id="Order-Name" name="Order-Name" type="text" value="@Model.Order.Status.Name" readonly> </div>
- Met dit als resultaat:
- We passen de Bll/model klasse van
- Partial Page
Zoals je hierboven ziet, moeten we de id's in de lijst onderaan nog vervangen door de respectievelijke namen. Maar we hebben die lijst op elke pagina van Order staan. I.p.v. elke pagina te wijzigen gaan we een nieuwe techniek leren, nameijk Partial Pages.- Maak een map met de naam Order in de Pages/Shared map.
- Maak daarin een Razor Page met de naam _ReadingAll.cshtml.
- Verwijder de
@Page
directive omdat dit geen volwaardige Razor Page is en voeg een model toe van het type generieke lijst met instanties van de BllOrder
klasse:---@Page---model List<Bll.Order> @{ } - Verplaats de volgende html uit de Razor pages met de naam /Order/
ReadingOne.cshtml, /Order/Index.cshtml, /Order/UpdatingOne.cshtml en /Order/InsertingOne.cshtml:
en plaats die in /Shared/Order/_ReadingAll.cshtml:<aside class="list"> <table class="list"> <thead> <tr> <th> </th> <th> Id </th> <th> Besteldatum </th> <th> Verzenddatum </th> <th> Opmerking </th> <th> Klant </th> <th> Verzedingsmethode </th> <th> Status </th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> <a href="Order/ReadingOne/@item.Id">Select</a> | </td> <td> @item.Id </td> <td> @item.OrderDate </td> <td> @item.ShippingDate </td> <td> @item.Comment </td> <td> @item.CustomerId </td> <td> @item.ShippingMethodId </td> <td> @item.StatusId </td> </tr> } </tbody> </table> </aside>
- In de de Razor pages met de naam /Order/
ReadingOne.cshtml, /Order/Index.cshtml, /Order/UpdatingOne.cshtml en /Order/InsertingOne.cshtml:
en plaats die in /Shared/Order/_ReadingAll.cshtml vervang je de html door de Partial taghelper en geef je de generieke lijstOrderList
mee door:@page @model BiblioAdmin.Pages.Order.IndexModel @{ } <h1>Order Index</h1> <a asp-page="./InsertingOne" asp-page-handler="Inserting">Inserting One </a> <partial name="Order/_ReadingAll" model="Model.OrderList"/>
- In de
InsertingOne
view willen we de lijst van de verzendmethoden, orderstus en klanten tonen een keuzelijst:- In de
Onget
methode van deInsertingOne
klasse in het Order/InsertingOne.cshtml.cs bestand lezen we verzendmethoden, orderstus en klanten in en geven die aan de view door met behulp van publieke properties:Model
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace BiblioAdmin.Pages.Order { public class InsertingOneModel : PageModel { private readonly Bll.Docent2Context dbContext; // voeg constructor toe om geïnjecteerde DBContext // te kunnen binnenkrijgen in deze klasse public InsertingOneModel(Bll.Docent2Context dbContext) { this.dbContext = dbContext; } [BindProperty] public Bll.Order Order { get; set; } public List<Bll.Order> OrderList { get; set; } public List<Bll.Customer> CustomerList { get; set; } public List<Bll.OrderStatus> OrderStatusList { get; set; } public List<Bll.ShippingMethod> ShippingMethodList { get; set; } public void OnGet() { OrderList = dbContext.Order.ToList(); CustomerList = dbContext.Customer.ToList(); OrderStatusList = dbContext.OrderStatus.ToList(); ShippingMethodList = dbContext.ShippingMethod.ToList(); } public ActionResult OnPostInsert(Bll.Order order) { if (!ModelState.IsValid) { // als er een foutief gegeven is ingetypt ga terug // de pagina en toon de fout return Page(); // return page, nog een nieuwe ingeven } // dbContext.Order.Update(order); dbContext.Order.Add(order); dbContext.SaveChanges(); // keer terug naar de index pagina van Order return RedirectToPage("Index"); } } }
- We passen de
Order/InsertingOne
view aan en lezen deoption
elementen uit deShippingMethodList
,OrderStatusList
enCustomerList
:@page @model BiblioAdmin.Pages.Order.InsertingOneModel @{ } <h3>InsertingOne Order</h3> <hr /> <br /> <form method="post"> <fieldset> <div> <label asp-for="Order.OrderDate"></label> <input asp-for="Order.OrderDate" /> </div> <div> <label asp-for="Order.ShippingDate"></label> <input asp-for="Order.ShippingDate" /> </div> <div> <label asp-for="Order.Comment"></label> <textarea asp-for="Order.Comment"></textarea> </div> <div> <label asp-for="Order.CustomerId"></label>
<input asp-for="Order.CustomerId" /><select id="Order_CustomerId" name="Order.CustomerId"> @foreach (var item in Model.CustomerList) { <option value="@item.Id">@item.FirstName @item.LastName</option> } </select> </div> <div> <label asp-for="Order.ShippingMethodId"></label><input asp-for="Order.ShippingMethodId" /><select id="Order_ShippingMethodId" name="Order.ShippingMethodId"> @foreach (var item in Model.ShippingMethodList) { <option value="@item.Id">@item.Name</option> } </select> </div> <div> <label asp-for="Order.StatusId"></label><input asp-for="Order.StatusId" /><select id="Order_StatusId" name="Order.StatusId"> @foreach (var item in Model.OrderStatusList) { <option value="@item.Id">@item.Name</option> } </select> </div> </fieldset> <div> <button type="submit" value="Insert" asp-page-handler="Insert">Insert</button> </div> </form> <partial name="Order/_ReadingAll" model="Model.OrderList" />
- In de
- In de
Order/UpdatingOne
view moeten we de keuzelijst ook opvullen met elementen uit deShippingMethodList
,OrderStatusList
enCustomerList
:
. Maar er komt iets extra bij: in het keuzeveld moet de naam van de ingegeven verzendmehode, klant en orderstatus komen te staan:- In de
Onget
methode van deUpdatingOne
klasse in het Order/UpdatingOne.cshtml.cs bestand lezen we verzendmethoden, orderstus en klanten in en geven die aan de view door met behulp van publieke properties:Model
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace BiblioAdmin.Pages.Order { public class UpdatingOneModel : PageModel { private readonly Bll.Docent2Context dbContext; // voeg constructor toe om geïnjecteerde DBContext // te kunnen binnenkrijgen in deze klasse public UpdatingOneModel(Bll.Docent2Context dbContext) { this.dbContext = dbContext; } [BindProperty] public Bll.Order Order { get; set; } public List<Bll.Order> OrderList { get; set; } public List<Bll.Customer> CustomerList { get; set; } public List<Bll.OrderStatus> OrderStatusList { get; set; } public List<Bll.ShippingMethod> ShippingMethodList { get; set; } public void OnGet(int? id) { this.Order = dbContext.Order.SingleOrDefault(m => m.Id == id); Order.ShippingMethod = dbContext.ShippingMethod.SingleOrDefault(m => m.Id == this.Order.ShippingMethodId); Order.Status = dbContext.OrderStatus.SingleOrDefault(m => m.Id == this.Order.StatusId); Order.Customer = dbContext.Customer.SingleOrDefault(m => m.Id == this.Order.CustomerId); OrderList = dbContext.Order.ToList(); CustomerList = dbContext.Customer.ToList(); OrderStatusList = dbContext.OrderStatus.ToList(); ShippingMethodList = dbContext.ShippingMethod.ToList(); } public ActionResult OnPostUpdate(Bll.Order order) { if (!ModelState.IsValid) { // als er een foutief gegeven is ingetypt ga terug // de pagina en toon de fout return Page(); // return page } // dbContext.OrderStatus.Update(orderStatus); dbContext.Order.Update(order); dbContext.SaveChanges(); // keer terug naar de index pagina van OrderStatus return RedirectToPage("Index"); } } }
- We passen vervolgens de
UpdatingOne
view aan:@page "{id}" @model BiblioAdmin.Pages.Order.UpdatingOneModel @{ } <h3>UpdatingOne Order</h3> <hr /> <br /> <form method="post"> <fieldset> <div> <label asp-for="Order.Id"></label> <input asp-for="Order.Id" readonly /> </div> <div> <label asp-for="Order.OrderDate"></label> <input asp-for="Order.OrderDate" /> </div> <div> <label asp-for="Order.ShippingDate"></label> <input asp-for="Order.ShippingDate" /> </div> <div> <label asp-for="Order.Comment"></label> <textarea asp-for="Order.Comment"></textarea> </div> <div> <label asp-for="Order.CustomerId"></label>
<input asp-for="Order.CustomerId" /><select id="Order-CustomerId" name="Order.CustomerId"> @foreach (var item in Model.CustomerList) { <option value="@item.Id" selected="@(item.Id == Model.Order.CustomerId ? true : false)"> @item.FirstName @item.LastName </option> } </select> </div> <div> <label asp-for="Order.ShippingMethodId"></label><input asp-for="Order.ShippingMethodId" /><select id="Order-ShippingMethodId" name="Order.ShippingMethodId"> @foreach (var item in Model.ShippingMethodList) { <option value="@item.Id" selected="@(item.Id == Model.Order.ShippingMethodId ? true : false)"> @item.Name </option> } </select> </div> <div> <label asp-for="Order.StatusId"></label><input asp-for="Order.StatusId" /><select id="Order-StatusId" name="Order.StatusId"> @foreach (var item in Model.OrderStatusList) { <option value="@item.Id" selected="@(item.Id == Model.Order.StatusId ? true : false)"> @item.Name </option> } </select> </div> </fieldset> <div> <button type="submit" value="Update" asp-page-handler="Update">Update</button> </div> </form> <partial name="Order/_ReadingAll" model="Model.OrderList" />
- In de
Opdracht
- Maak zelf de 1 op1 voor OrderStatus, ShippingMethode en Customer zoals in het lesvoorbeeld.
- Maak een Partial page voor ReadingAll voor alle entiteiten, nl Order, Customer, OrderItem, ShippingMethod, Customer, OrderStatus en Book.
- Zorg ervoor de de 1 op 1 in tussen OrderItem en Book zo implementeert dat als je een orderitem toevoegt, van een lijst kan selecteren uit boeken.
- De foreign key voor OrderId geef je letterlijk in met de Id van de order waaraan je de OderItem wilt koppelen. In de volgende les leren we de 1 op n relatie tussen Order en OrderItem implemeren.
- Maak een lay-out pagina voor de Admin pagina's. Als voorbeeld van de lay-out verwijs ik naar: Fric-frac Wireframes Person.
- Stel enkele eenvoudige CSS regels op om de lay-out van de Admin pagina's gebruiksvriendelijk te maken. Maak gebruik van GRID en Flexbox om de hoofdlay-out van de pagina's te maken. Info over flex en grid:
- Deze opdracht maakt deel uit van de eindopdracht.
2020-12-13 19:22:29